package mundo;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.MulticastSocket;
import java.net.ServerSocket;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import commons.IAplicacionLocal;

public class P2P extends Observable
{
	public final static String SEPARADOR = "#";
	
	public final static String AGREGAR = "AGREGAR";
	public final static String ELIMINAR = "ELIMINAR";
	
	private ArrayList<IAplicacionLocal> locales;
	private ArrayList<AplicacionRemota> remotas;
	private MulticastSocket socketMulticast;

	private int puerto;
	private InetAddress grupoMulticast;
	
	public P2P( int port, String ipMulticast ) throws Exception
	{
		puerto = port;
		grupoMulticast = InetAddress.getByName( ipMulticast );
		socketMulticast = new MulticastSocket( port );
		socketMulticast.joinGroup( grupoMulticast );
		remotas = new ArrayList<AplicacionRemota>( );
		locales = new ArrayList<IAplicacionLocal>( );
		
		ThreadRecibirMensajeMulticast trm = new ThreadRecibirMensajeMulticast( socketMulticast, this );
		trm.start();
	}
	
	//////////////////////////////////////////////////////////
	//					Funciones Auxiliares
	//////////////////////////////////////////////////////////
	
	public static int darPuertoLibre( )
	{
		for( int i = 27010; i < 30000 ; i++ )
			if( isFree(i) )
				return i;
		return -1;
	}
	
	public static boolean isFree( int port ) 
	{
        try {
            ServerSocket ssocket = new ServerSocket(port);
            ssocket.close();
            return true;
        } catch (IOException ex) {
            return false;
        }
    }
	
	public ArrayList<IAplicacionLocal> darListaLocales( )
	{
		return locales;
	}
	
	public String[][] darLocales( )
	{
		String[][] retorno = new String[locales.size()][3];
		for( int i=0; i<locales.size(); i++ )
		{
			retorno[i][0] = locales.get(i).darNombre();
			retorno[i][2] = locales.get(i).darPuerto() + "";
			retorno[i][1] = "";
			for( int j = 0; j < locales.get(i).darParametros().length ; j++ )
				retorno[i][1] += locales.get(i).darParametros()[j] + ",";
			if( !retorno[i][1].equals("") )
				retorno[i][1] = retorno[i][1].substring( 0, retorno[i][1].length()-1 );
		}
		return retorno;
	}
	
	public String[][] darRemotas( )
	{
		String[][] retorno = new String[remotas.size()][3];
		for( int i=0; i<remotas.size(); i++ )
		{
			retorno[i][0] = remotas.get(i).darNombre();
			retorno[i][1] = remotas.get(i).darIP() + ":" + remotas.get(i).darPuerto();
			retorno[i][2] = "";
			for( int j = 0; j < remotas.get(i).darParametros().length ; j++ )
				retorno[i][2] += remotas.get(i).darParametros()[j] + ",";
			if( !retorno[i][2].equals("") )
				retorno[i][2] = retorno[i][2].substring( 0, retorno[i][2].length()-1 );
		}
		return retorno;
	}
	
	//////////////////////////////////////////////////////////
	//					Funciones Multicast
	//////////////////////////////////////////////////////////
	
	public void enviarMensajeMulticast( String mensaje ) throws IOException
	{
		DatagramPacket p = new DatagramPacket( mensaje.getBytes(), mensaje.length(), grupoMulticast, puerto );
		socketMulticast.send( p );
	}
	
	public void procesarMensajeMulticast( String mensaje )
	{
		if( mensaje.startsWith( AGREGAR ) )
		{
			String[] datos = mensaje.split( SEPARADOR );
			for( int i = 0; i < remotas.size() ; i++ )
			{
				if( remotas.get( i ).darNombre().equals( datos[ 1 ] ) )
				{
					System.out.println( "Aplicacion con ese nombre ya existe" );
					return;
				}
			}
			for( int i = 0; i < locales.size() ; i++ )
				if( locales.get( i ).darNombre().equals( datos[ 1 ] ) )
					return;
			
			String params[] = new String[ datos.length - 5 ];
			for( int i = 0; i < params.length ; i++ )
				params[i] = datos[ 4 + i ];
			
			remotas.add( new AplicacionRemota( datos[1], datos[2], Integer.parseInt( datos[3] ), params ) );
			setChanged();
			notifyObservers();
		}
		else if( mensaje.startsWith( ELIMINAR ) )
		{
			for( int i = 0; i < remotas.size() ; i++ )
			{
				if( remotas.get( i ).darNombre().equals( mensaje.split( SEPARADOR )[ 1 ] ) )
				{
					remotas.remove( i );
					setChanged();
					notifyObservers();
					break;
				}
			}
		}
		else
			System.out.println( "Mensaje desconocido" );
	}
	
	//////////////////////////////////////////////////////////
	//				Funciones Aplicacion Local
	//////////////////////////////////////////////////////////
	
	public void agregarAplicacion( String nombre, String nombreClase, int puerto ) throws Exception
	{
		for( int i = 0; i < remotas.size() ; i++ )
		{
			if( remotas.get( i ).darNombre().equals( nombre ) )
			{
				throw new Exception( "Aplicacion remota con ese nombre ya existe" );
			}
		}
		for( int i = 0; i < locales.size() ; i++ )
		{
			if( locales.get( i ).darNombre().equals( nombre ) )
			{
				throw new Exception( "Aplicacion local con ese nombre ya existe" );
			}
		}
		
		try {
			IAplicacionLocal a = darAplicacion( nombreClase );
			a.setNombre( nombre );
			a.setPuerto( puerto );
			locales.add( a );
			
			Thread tsa = new Thread( new ThreadServirAplicacion( a, this ) );
			tsa.start();
			
			String params = "";
			for( int i = 0; i < a.darParametros().length ; i++ )
				params += a.darParametros()[ i ] + SEPARADOR;
			
			enviarMensajeMulticast( AGREGAR + SEPARADOR
										+ nombre + SEPARADOR 
										+ InetAddress.getLocalHost().getHostAddress() + SEPARADOR
										+ puerto + SEPARADOR
										+ params );
			
			setChanged();
			notifyObservers();
		} catch ( Exception e ) {
			e.printStackTrace();
			throw new Exception( "Aplicacion no se pudo cargar bien: " + e.getMessage() );
		}
	}
	
	private static IAplicacionLocal darAplicacion( String nombreClase ) throws Exception
	{
		return (IAplicacionLocal)Class.forName( "aplicaciones." + nombreClase ).newInstance();
	}

	public void borrarAplicacion( String nombre ) throws IOException
	{
		for( int i = 0; i < locales.size() ; i++ )
			if( locales.get( i ).darNombre().equals( nombre ) )
			{
				enviarMensajeMulticast( ELIMINAR + SEPARADOR + nombre + SEPARADOR );
				locales.remove( i );
			}
		setChanged();
		notifyObservers();
	}
	
	public void borrarTodasLasAplicaciones( )
	{
		for( int i = 0; i < locales.size() ; i++ )
		{
				try {
					enviarMensajeMulticast( ELIMINAR + SEPARADOR + locales.get(i).darNombre() + SEPARADOR );
				} catch (IOException e) {
				}
				locales.remove( i );
		}
	}
	
	public void usarAplicacion( Observer ob, String nombre, String[] params )
	{
		for( int i = 0; i < remotas.size() ; i++ )
		{
			if( remotas.get( i ).darNombre().equals( nombre ) )
			{
				ThreadUsarAplicacion tua = new ThreadUsarAplicacion( remotas.get( i ), ob, params );
				Thread t = new Thread( tua );
				t.start();
			}
		}
	}
	
	public void setCambiado( )
	{
		setChanged();
		notifyObservers();
	}
}
